#include "block_file.h"
#include <vector>
#include <queue>

BlockFile::BlockFile(std::string path,openmode mode,const RegisterFactory &regFactory,unsigned int blockSize) 
	: StreamFile(path,mode,regFactory),blockFactory(blockSize),block_table(path,blockSize){
	
	file.open(path.c_str(), std::fstream::binary | p_mode(mode));
	if(!file){
		file.open(path.c_str(), std::fstream::binary | p_mode(mode) | std::fstream::trunc);
		this->fcreated = true;
	}
}

BlockFile::~BlockFile(){
}

FilePointer* BlockFile::write(const Register& reg){
	std::pair<BlockTableRegister,bool> p;
	unsigned int blockSize = blockFactory.getBlockSize();
	Block *block;
	
	if( reg.getSize()+Block::getDataControlSize()+Block::getHeaderSize() > blockSize)
		return writeChainedRegister(reg);
	
	// Si hay espacio en algun bloque
	p = block_table.getAvailBlock(reg);
	if( p.second ){
		block = readBlock((p.first.block_number - 1) * blockSize);
	}else{
		// Si no hay espacio, creo uno nuevo
		block = new Block(*factory,blockSize);
	}
	
	// Agrego el registro
	unsigned short reg_number = block->addRegister(reg);
	
	writeBlock(*block,(p.first.block_number-1) * blockSize);	

	// Actualizo el espacio libre del bloque
	p.first.free_space = block->getFree();
	block_table.writeBlockTableRegister(p.first);
	delete block;
	
	return new BlockPointer( reg_number,p.first.block_number);
}

BlockPointer* BlockFile::writeChainedRegister(const Register& reg){
	BlockPointer* ret = new BlockPointer(0,1);
	unsigned int free_space = blockFactory.getBlockSize()-Block::getHeaderSize()-Block::getDataControlSize()-ChainRegister::getPointerSize();
	unsigned int nBlocks = reg.getSize()/(free_space);

	/* redondeo hacia arriba */
	if( reg.getSize()%(free_space) )
		nBlocks++;
		
	/* Busco los bloques donde va a ir la cadena */
	std::list<unsigned long int>block_numbers;
	block_table.createChain(block_numbers,nBlocks);
	/* El usuario se queda con el primero de la cadena */
	ret->block = block_numbers.front();
	block_numbers.pop_front();
	
	/* Creo todos los bloques y meto los pedazos de registro en cada bloque*/
	unsigned int offset = 0;
	unsigned int curr_block = ret->block;
	char* data = new char[reg.getSize()];
	(*factory)(reg,data);
	for(unsigned int c=0;c<nBlocks;c++){
		Block* block = new Block(blockFactory.getChainFactory(),blockFactory.getBlockSize());
		unsigned long int next;
		ChainRegister* cr;		
				
		block->setChained(true);
		if(c==0){
			block->setChainBegining(true);
		}
		
		if(c==nBlocks-1){
			block->setChainEnd(true);
			next = 0;
			cr = new ChainRegister(data,offset,reg.getSize()-(free_space*(nBlocks-1)),next);
		}else{
 			next = block_numbers.front();
			cr = new ChainRegister(data,offset,free_space,next);
			block_numbers.pop_front();
		}
				
		
		ret->reg = block->addRegister(*cr);
		offset += free_space;		
		
		block_table.writeBlockTableRegister(BlockTableRegister(0,curr_block));
		writeBlock(*block,(curr_block-1)*(blockFactory.getBlockSize()));				
		curr_block = next;

		delete cr;
		delete block;
	}
	delete[] data;
	
	return ret;
}

Register* BlockFile::read(const FilePointer &pointer){
	bool block_exist = false;
	Register *ret = NULL;
	unsigned int blockSize = blockFactory.getBlockSize();
	const BlockPointer& bp = dynamic_cast<const BlockPointer&>(pointer);
	
	//veo si existe el bloque
	block_exist = block_table.find(bp);
		
	//Si existe, recupero el bloque
	if(block_exist){
		Block* block = readBlock( (bp.block - 1) * blockSize);
		
		//Recupero el registro dentro del bloque
		if(!block->isChained())
			ret = block->getRegister(bp.reg);
		else if(block->isChainBegining())
			ret = readChainedRegister(*block);
		else	
			this->ferror = true;
		
		delete block;
	}

	return ret;
}

Register* BlockFile::readChainedRegister(Block& block){
	bool end = false;
	std::vector<Register*>chain;
	/* Si esta encadenado no puede tener otro numero de registro */
	ChainRegister* temp = dynamic_cast<ChainRegister*>(block.getRegister(1));
	unsigned int totalSize = temp->getDataSize();
	unsigned int next = temp->getNext();
	unsigned int blockSize = blockFactory.getBlockSize();
	chain.push_back(temp);	
	
	/* Levanto todas las partes */		
	while(!end){
		Block* ptrBlock = readBlock((next - 1) * blockSize);
		temp = dynamic_cast<ChainRegister*>(ptrBlock->getRegister(1));
		totalSize += temp->getDataSize();
		next = temp->getNext();
		chain.push_back(temp);		

		end = ptrBlock->isChainEnd();
		delete ptrBlock;
	}
	
	/* Junto todas las partes */
	Register* reg;
	char* bytes = new char[totalSize];
	char* ptr = bytes;
	for(std::vector<Register*>::iterator it = chain.begin();it!=chain.end();++it){
		temp = dynamic_cast<ChainRegister*>(*it);
		memcpy(ptr,temp->peekData(),temp->getDataSize());
		ptr += temp->getDataSize();
		delete temp;
	}
	reg = (*factory)(bytes);
	delete[] bytes;
	return reg;
}

void BlockFile::free(const FilePointer &pointer){
	//Recupero el bloque
	unsigned int blockSize = blockFactory.getBlockSize();
	const BlockPointer& bp = dynamic_cast<const BlockPointer&>(pointer);	
	
	Block* block = readBlock( (bp.block - 1) * blockSize );
	
	
	if(block->isChained()){
		freeChainedRegister(bp);
	}else{
		//Elimino el registro dentro del bloque
		block->removeRegister(bp.reg);
	
		//Guardo nuevamente el bloque en el archivo...
		writeBlock(*block, (bp.block-1) * blockSize);

		// Actualizo  en la tabla de bloques
		block_table.writeBlockTableRegister(BlockTableRegister(block->getFree(),bp.block));
	}	
	delete block;		
}

void BlockFile::freeChainedRegister(const BlockPointer& bp){
	bool end = false;
	unsigned int blockSize = blockFactory.getBlockSize();
	unsigned int temp_block_number = bp.block;
		
	/* Levanto todas las partes */		
	while(!end){		
		Block* ptrBlock = readBlock((temp_block_number - 1) * blockSize );
		ChainRegister* reg = dynamic_cast<ChainRegister*>(ptrBlock->getRegister(bp.reg));
		block_table.writeBlockTableRegister(BlockTableRegister(blockSize,temp_block_number));
		
		end = ptrBlock->isChainEnd();
		
		temp_block_number = reg->getNext();
		delete reg;
		delete ptrBlock;
	}
}

FilePointer *BlockFile::update(const Register &reg, FilePointer &pointer){
	//Elimino el registro
	free(pointer);
	//Inserto el nuevo registro en el primer bloque con espacio...
	return write(reg);
}

void BlockFile::writeBlock(Block& block,unsigned int offset){
	char *data = new char[blockFactory.getBlockSize()];	
	/* Para que no rompa el valgrind */
	memset(data,0,blockFactory.getBlockSize());
	file.clear();
	file.seekp( offset, std::fstream::beg);
	blockFactory(block, data);
	file.write(data, blockFactory.getBlockSize());
	delete[] data;
}

Block* BlockFile::readBlock(unsigned int offset){
	Block* ret = NULL;
	file.clear();			
	file.seekg(offset, std::fstream::beg);
	char *data = new char[blockFactory.getBlockSize()];
	/* Para que no rompa el valgrind */
	memset(data,0,blockFactory.getBlockSize());
	file.read(data, blockFactory.getBlockSize());
	ret = blockFactory(data,*factory);
	delete[] data;
	return ret;
}

std::ostream& BlockFile::toOstream(std::ostream& out){
	//Recupero cada bloque
	unsigned int blockSize = blockFactory.getBlockSize();
	int cant = 1;
	Block* block;
	out<< "------Comienzo Archivo de Bloques------ " <<std::endl;
	while(!file.eof()){
		out<< "\t--Inicio Bloque Nro = " << cant << " ---" << std::endl;
		block = readBlock((cant-1)*blockSize);
		
		if(block->isChained()){
			out << "\tEl bloque esta encadenado." << std::endl;
			if(block->isChainBegining()){
				out << "\tEl bloque es el principio de una cadena." << std::endl;
				out << "\t - Inicio Registro - " << std::endl;
				Register* reg = readChainedRegister(*block);
				reg->toOstream(out);
				delete reg;
				out << "\t - Fin Registro - " << std::endl;
			}
			else if(block->isChainEnd())
				out << "\tEl bloque es el fin de una cadena." << std::endl;			
		}else{
			out<< "\tCantidad : "<< block->getQuantity() << std::endl;
			for(int i=0;i<block->getQuantity();i++)
			{
				out<<"\tNro. Reg: "<< block->getRegNumber(i) << "  Offset: "<< block->getRegOffset(i) << std::endl;
			}
			out << "\tEspacio libre: "<< block->getFree() << std::endl;
			out<< "\t**Incio Registros**" << std::endl;
			for(int i=0;i<block->getQuantity();i++){
				out << "\tRegistro: " << i+1 << std::endl;
				Register *reg = (*factory)(block->getReg(i));
				reg->toOstream(out);
				delete reg;
			}
			out<< "\t**Fin Registros**" << std::endl;
		}
		out<< "\t--Fin Bloque-- " << std::endl;
		
		cant++;
		delete block;
		file.peek(); /* activo el EOF por adelantado */
	}
	out<< "------Fin Archivo de Bloques------ " <<std::endl;
	
	return out;
}

FilePointer* BlockFile::first(){
	// TODO -> tira una excepcion dado que no esta implementada esta funcionalidad
	throw ("BlockFile first -> No implementado !!!!!");
	return NULL;
}

FilePointer* BlockFile::last(){
	// TODO -> tira una excepcion dado que no esta implementada esta funcionalidad
	throw ("BlockFile last -> No implementado !!!!!");
	return NULL;
}

void BlockFile::peek(const FilePointer& pointer,bool back){
	// TODO -> tira una excepcion dado que no esta implementada esta funcionalidad
	throw ("BlockFile peek -> No implementado !!!!!");
}

Register* BlockFile::readNext(FilePointer& pointer,bool back){
	// TODO -> tira una excepcion dado que no esta implementada esta funcionalidad
	throw ("BlockFile read_next -> No implementado !!!!!");
	return NULL;
}
